package cn.anecansaitin.jep.parser.ast.node.expression;

import cn.anecansaitin.jep.Token;
import cn.anecansaitin.jep.TokenType;
import cn.anecansaitin.jep.parser.ast.node.NodeCreationResult;
import cn.anecansaitin.jep.types.TypeInfo;
import org.jetbrains.annotations.Nullable;

/// 二元表达式
public final class Binary extends Expression {
    private final Expression left;
    private final Token operator;
    private final Expression right;

    private Binary(Expression left, Token operator, Expression right, TypeInfo resultType, int line, int column) {
        super(resultType, line, column);
        this.left = left;
        this.operator = operator;
        this.right = right;
    }

    public Expression left() {
        return left;
    }

    public Token operator() {
        return operator;
    }

    public Expression right() {
        return right;
    }

    @Nullable
    private static TypeInfo analyzeTypeResult(Expression left, Token operator, Expression right) {
        return switch (operator.type()) {
            case AND, OR, EQUAL, NOT_EQUAL, LESS_THAN, GREATER_THAN, LESS_THAN_EQUAL, GREATER_THAN_EQUAL ->
                    bool(left, operator, right);
            case BIT_LEFT, BIT_RIGHT, BIT_RIGHT_UNSIGNED -> bitShift(left, right);
            case PLUS, MINUS,
                 MODULO, DIVIDE, MULTIPLY -> normal(left, right);
            case POWER -> power(left, right);
            default -> null;
        };
    }

    @Nullable
    private static TypeInfo bool(Expression left, Token operator, Expression right) {
        TypeInfo leftType = left.resultType();
        TypeInfo rightType = right.resultType();

        if (leftType.isNumber() && rightType.isNumber()) {
            return TypeInfo.BOOLEAN;
        }

        return switch (operator.type()) {
            case AND, OR, EQUAL, NOT_EQUAL ->
                    leftType == TypeInfo.BOOLEAN && rightType == TypeInfo.BOOLEAN ? TypeInfo.BOOLEAN : null;
            default -> null;
        };
    }

    @Nullable
    private static TypeInfo bitShift(Expression left, Expression right) {
        TypeInfo leftType = left.resultType();
        TypeInfo rightType = right.resultType();

        return switch (leftType) {
            case INT, CHAR -> switch (rightType) {
                case CHAR, INT, LONG -> TypeInfo.INT;
                default -> null;
            };
            case LONG -> switch (rightType) {
                case CHAR, INT, LONG -> TypeInfo.LONG;
                default -> null;
            };
            default -> null;
        };
    }

    @Nullable
    private static TypeInfo normal(Expression left, Expression right) {
        TypeInfo leftType = left.resultType();
        TypeInfo rightType = right.resultType();

        return switch (leftType) {
            case INT -> switch (rightType) {
                case DOUBLE -> TypeInfo.DOUBLE;
                case FLOAT -> TypeInfo.FLOAT;
                case LONG -> TypeInfo.LONG;
                case INT, CHAR -> TypeInfo.INT;
                case STRING -> TypeInfo.STRING;
                default -> null;
            };
            case LONG -> switch (rightType) {
                case DOUBLE -> TypeInfo.DOUBLE;
                case FLOAT -> TypeInfo.FLOAT;
                case LONG, INT, CHAR -> TypeInfo.LONG;
                case STRING -> TypeInfo.STRING;
                default -> null;
            };
            case FLOAT -> switch (rightType) {
                case DOUBLE -> TypeInfo.DOUBLE;
                case FLOAT, LONG, INT, CHAR -> TypeInfo.FLOAT;
                case STRING -> TypeInfo.STRING;
                default -> null;
            };
            case DOUBLE -> switch (rightType) {
                case DOUBLE, FLOAT, LONG, INT, CHAR -> TypeInfo.DOUBLE;
                case STRING -> TypeInfo.STRING;
                default -> null;
            };
            case STRING -> switch (rightType) {
                case STRING, INT, LONG, FLOAT, DOUBLE, CHAR, BOOLEAN -> TypeInfo.STRING;
                default -> null;
            };
            case CHAR -> switch (rightType) {
                case INT, CHAR -> TypeInfo.INT;
                case LONG -> TypeInfo.LONG;
                case FLOAT -> TypeInfo.FLOAT;
                case DOUBLE -> TypeInfo.DOUBLE;
                case STRING -> TypeInfo.STRING;
                default -> null;
            };
            default -> null;
        };
    }

    @Nullable
    private static TypeInfo power(Expression left, Expression right) {
        TypeInfo leftType = left.resultType();
        TypeInfo rightType = right.resultType();
        return leftType.isNumber() && rightType.isNumber() ? TypeInfo.DOUBLE : null;
    }

    public static NodeCreationResult<Binary> create(Expression left, Token operator, Expression right, int line, int column) {
        TypeInfo typeInfo = analyzeTypeResult(left, operator, right);

        if (typeInfo == null) {
            return NodeCreationResult.fail("运算符 " + operator.type() + " 不能应用于 " + left.resultType() + "、" + right.resultType());
        }

        return NodeCreationResult.success(new Binary(left, operator, right, typeInfo, line, column));
    }
}
